#include <cassert>
#include <vector>
#include "curling/game.hpp"
#include "curling/rink.hpp"
#include "curling/stone.hpp"

namespace curling {

game::game(
  player_type player_one_type,
  player_type player_two_type,
  int regular_ends,
  int player_stones )
: _player_one_type(player_one_type),
  _player_two_type(player_two_type),
  _player_one_time(default_game::player_time),
  _player_two_time(default_game::player_time),
  _current_state(state_initial),
  _current_turn(turn_none),
  _player_one_clock_ticking(false),
  _player_two_clock_ticking(false),
  _regular_endings(regular_ends),
  _current_ending(0)
{
  assert(regular_ends >= 2);
  assert(regular_ends <= 8);
  assert(player_stones >= 2);
  assert(player_stones <= 8);
  
  /* Construct rink. */
  _rink_played = boost::shared_ptr<rink>(new rink());
  
  /* Add stones to rink. */
  for (int i=0; i<player_stones; ++i)
  {
    _rink_played->add_stone(boost::shared_ptr<stone>(new stone(color_red)));
    _rink_played->add_stone(boost::shared_ptr<stone>(new stone(color_blue)));
  }
  
  /* Let the game begin. */
  switch_state(state_beginning_game);
}

game::~game()
{
}

player_type game::player_one_type() const
{
  return _player_one_type;
}

player_type game::player_two_type() const
{
  return _player_two_type;
}

bool game::player_one_turn() const
{
  return _current_turn == turn_player_one;
}

bool game::player_two_turn() const
{
  return _current_turn == turn_player_two;
}

double game::player_one_time() const
{
  return _player_one_time;
}

double game::player_two_time() const
{
  return _player_two_time;
}

int game::player_one_stones_left() const
{
  int result = 0;
  
  for (rink::stone_iterator i = _rink_played->begin_stones();
       i != _rink_played->end_stones();
       ++i)
  {
    // If color matches player color and the stone has not been played
    if (((*i)->color() == color_red) && (!(*i)->played()))
    {
      result++;
    }
  }
  
  return result;
}

int game::player_two_stones_left() const
{
  int result = 0;
  
  for (rink::stone_iterator i = _rink_played->begin_stones();
       i != _rink_played->end_stones();
       ++i)
  {
    // If color matches player color and the stone has not been played
    if (((*i)->color() == color_blue) && (!(*i)->played()))
    {
      result++;
    }
  }
  
  return result;
}

bool game::initial() const
{
  return current_state() == state_initial;
}

bool game::beginning_game() const
{
  return current_state() == state_beginning_game;
}

bool game::beginning_end() const
{
  return current_state() == state_beginning_end;
}

bool game::player_beginning_shot() const
{
  return current_state() == state_player_beginning_shot;
}

bool game::player_aiming() const
{
  return current_state() == state_player_aiming;
}

bool game::player_delivering() const
{
  return current_state() == state_player_delivering;
}

bool game::player_sweeping() const
{
  return current_state() == state_player_sweeping;
}

bool game::player_ending_shot() const
{
  return current_state() == state_player_ending_shot;
}

bool game::ending_end() const
{
  return current_state() == state_ending_end;
}

bool game::ending_game() const
{
  return current_state() == state_ending_game;
}

bool game::final() const
{
  return current_state() == state_final;
}

void game::begin_game()
{
  switch (current_state())
  {
  case state_beginning_game:
    // player one always starts the game
    _current_turn = turn_player_one;
    
    switch_state(state_beginning_end);
    break;
  
  default:
    assert(false);
    break;
  }
}

void game::begin_end()
{
  switch (current_state())
  {
  case state_beginning_end:
    // reset stones
    for (rink::stone_iterator i = _rink_played->begin_stones();
         i != _rink_played->end_stones();
         ++i)
    {
      (*i)->reset();
    }
    
    switch_state(state_player_beginning_shot);
    break;
  
  default:
    assert(false);
    break;
  }
}

void game::player_begin_shot()
{
  switch (current_state())
  {
  case state_player_beginning_shot:
    //Find a stone that has not been played
    if (player_one_turn())
    {
      find_and_setup_stone(color_red);
    }
    else
    {
      find_and_setup_stone(color_blue);
    }
    switch_state(state_player_aiming);
    break;
  
  default:
    assert(false);
    break;
  }
}

void game::player_aim( vector2d target )
{
}

void game::player_deliver( double direction, double power, double spin )
{
  assert(direction >= -1.0);
  assert(direction <= 1.0);
  assert(power >= 0.0);
  assert(power <= 1.0);
  assert(spin >= -1.0);
  assert(spin <= 1.0);
  
  switch (current_state())
  {
  case state_player_aiming:
  {
    _player_direction = direction;
    _player_power = power;
    _player_spin = spin;
    
    double velocity = 2.0+(_player_power*2.5);
    double pi = 3.1415;
    double aim_to_rad = (0.5*pi)-(_player_direction*(0.1225*pi));
    double xacc = velocity*std::cos(aim_to_rad);
    double yacc = velocity*std::sin(aim_to_rad);
    _stone_played->accellerate(vector2d(xacc, yacc), 0.5*3.1415*_player_spin);
    switch_state(state_player_delivering);
  } break;
  
  default:
    assert(false);
    break;
  }
}

void game::player_sweep()
{
  switch (current_state())
  {
  case state_player_sweeping:
    // fill in here
    break;
  
  default:
    assert(false);
    break;
  }
}

void game::player_end_shot()
{
  switch (current_state())
  {
  case state_player_ending_shot:
    // tag stones
    update_point_stones();
    
    // determine next player
    if ((player_one_stones_left() > 0) || (player_two_stones_left() > 0))
    {
      //Next player
      switch (_current_turn)
      {
      case turn_player_one:
        _current_turn = turn_player_two;
        break;
      
      case turn_player_two:
        _current_turn = turn_player_one;
        break;
      
      case turn_none:
      default:
        assert(false);
        break;
      }
      switch_state(state_player_beginning_shot);
    }
    else
    {
      //End over
      switch_state(state_ending_end);
    }
    break;
  
  default:
    assert(false);
    break;
  }
}

void game::end_end()
{
  switch (current_state())
  {
  case state_ending_end:
    // record score
    record_score();
    
    // if last ending => end game, else begin new end
    if (_current_ending == _regular_endings-1)
    {
      switch_state(state_ending_game);
    }
    else
    {
      // determine player turn
      // end winning player starts
      if (player_one_end_score(_current_ending) > player_two_end_score(_current_ending))
      {
        _current_turn = turn_player_one;
      }
      else
      if (player_one_end_score(_current_ending) < player_two_end_score(_current_ending))
      {
        _current_turn = turn_player_two;
      }
      else
      {
        // same player starts this end as last end
        switch (_current_turn)
        {
        case turn_player_one:
          _current_turn = turn_player_two;
          break;
        
        case turn_player_two:
          _current_turn = turn_player_one;
          break;
        
        default:
          assert(false);
          break;
        }
      }
      
      _current_ending++;
      
      switch_state(state_beginning_end);
    }
    break;
  
  default:
    assert(false);
    break;
  }
}

void game::end_game()
{
  switch (current_state())
  {
  case state_ending_game:
    switch_state(state_final);
    break;
  }
}

boost::shared_ptr<rink> game::rink_played()
{
  return _rink_played;
}

boost::shared_ptr<stone> game::stone_played()
{
  return _stone_played;
}

void game::simulate( double dt )
{
  switch (current_state())
  {
  case state_player_aiming:
    break;
    
  case state_player_delivering:
    if (_stone_played->position().y() >= _rink_played->length() - _rink_played->hog_line())
    {
      switch_state(state_player_sweeping);
    }
    if (!_stone_played->moving())
    {
      _stone_played->remove_from_play();
    }
    if (!_stone_played->in_play())
    {
      switch_state(state_player_ending_shot);
    }
    break;
  
  case state_player_sweeping:
    if (!_stone_played->in_play() || !_stone_played->moving())
    {
      switch_state(state_player_ending_shot);
    }
    break;
    
  default:
    //this event may be called for any state
    break;
  }
  
  _rink_played->simulate(dt);
}

int game::regular_endings() const
{
  return _regular_endings;
}

int game::current_ending() const
{
  return _current_ending;
}

int game::player_one_end_score( int end ) const
{
  if (_player_one_ends.size() > end)
  {
    return _player_one_ends[end];
  }
  else
  {
    return 0;
  }
}

int game::player_two_end_score( int end ) const
{
  if (_player_two_ends.size() > end)
  {
    return _player_two_ends[end];
  }
  else
  {
    return 0;
  }
}

int game::player_one_score() const
{
  int sum = 0;
  for (int i=0; i<_player_one_ends.size(); ++i)
  {
    sum += _player_one_ends[i];
  }
  return sum;
}

int game::player_two_score() const
{
  int sum = 0;
  for (int i=0; i<_player_two_ends.size(); ++i)
  {
    sum += _player_two_ends[i];
  }
  return sum;
}

typedef std::pair<boost::shared_ptr<stone>, double> dist_pair;
 
struct dist_pair_compare
{
  bool operator()( const dist_pair& a, const dist_pair& b )
  {
    return a.second < b.second;
  }
};

void game::update_point_stones()
{
  // calculate distance from each stone to tee
  typedef std::pair<boost::shared_ptr<stone>, double> dist_pair;
  std::vector<dist_pair> distances;
  for (rink::stone_iterator i = _rink_played->begin_stones();
       i != _rink_played->end_stones();
       ++i)
  {
    // untag in case the stone was previously tagged
    (*i)->untag_as_point();
    // calculate distance to tee and put resulting pair into vector if the stone
    // touches the "house"
    if ((*i)->in_play())
    {
      double d = ((*i)->position() - vector2d(0.0, _rink_played->length() - _rink_played->tee_line())).magnitude();
      if (d < (_rink_played->circle3_radius()+(*i)->radius()))
      {
        distances.push_back(dist_pair(*i, d));
      }
    }
  }
  
  // sort in increasing order
  std::sort(distances.begin(), distances.end(), dist_pair_compare());
  
  if (distances.size() > 0)
  {
    stone_color current_color = distances[0].first->color();
    distances[0].first->tag_as_point();
    for (int i=1; i<distances.size(); ++i)
    {
      if (distances[i].first->color() == current_color)
      {
        distances[i].first->tag_as_point();
      }
      else
      {
        break;
      }
    }
  }
}

void game::record_score()
{
  int red_score = 0;
  int blue_score = 0;
  
  // go through all stones and sum points
  for (rink::stone_iterator i = _rink_played->begin_stones();
       i != _rink_played->end_stones();
       ++i)
  {
    if ((*i)->tagged_as_point())
    {
      switch ((*i)->color())
      {
      case color_red:
        red_score++;
        break;
      
      case color_blue:
        blue_score++;
        break;
      
      default:
        assert(false);
        break;
      }
    }
  }
  
  _player_one_ends.push_back(red_score);
  _player_two_ends.push_back(blue_score);
}

game::state game::current_state() const
{
  return _current_state;
}

void game::switch_state( state next_state )
{
  _current_state = next_state;
}

void game::find_and_setup_stone( stone_color color )
{
  for (rink::stone_iterator i = _rink_played->begin_stones();
       i != _rink_played->end_stones();
       ++i)
  {
    if (((*i)->color() == color) && (!(*i)->played()))
    {
      _stone_played = (*i);
      break;
    }
  }
  
  // Place stone one meter in front of hack
  _stone_played->reposition(vector2d(0.0, _rink_played->hack()+1.0), 0.0);
  _stone_played->put_into_play();
}

}